import { Directive, Input, ElementRef, Renderer2, Optional, Inject, HostListener, SimpleChanges, forwardRef, OnChanges, OnInit } from '@angular/core';
import { COMPOSITION_BUFFER_MODE, NG_VALUE_ACCESSOR } from '@angular/forms';

import { isString } from '../../util';

@Directive({
  // tslint:disable-next-line:directive-selector
  selector: 'input,textarea',
  providers: [
    {// 覆盖原有的 NG_VALUE_ACCESSOR
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => RuleDirective),
      multi: true
    }
  ]
})
export class RuleDirective implements OnChanges, OnInit {

  @Input() bzRule: string | string[];
  @Input() bzTrim = true;
  @Input() bzRuleCondition = 'and';

  // 是否正在进行输入法输入
  private _composing = false;
  private _value = '';
  private _rule: RegExp[];
  private _inputElement: HTMLInputElement;

  constructor(
    private elementRef: ElementRef,
    private renderer: Renderer2,
    @Optional() @Inject(COMPOSITION_BUFFER_MODE) private _compositionMode: boolean
  ) {
    this._inputElement = this.elementRef.nativeElement;
  }

  @HostListener('input', ['$event']) onInput($event) {
    $event.stopImmediatePropagation();
    this._input($event.target.value);
  }
  @HostListener('blur', ['$event']) onBlur(e) {
    this.onTouched();
  }
  @HostListener('compositionstart') onCompositionstart() {
    this._compositionStart();
  }
  @HostListener('compositionend', ['$event.target.value']) onCompositionend(value) {
    this._compositionEnd(value);
  }

  onChange = (_: any) => { };
  onTouched = () => { };

  writeValue(value: any): void {
    const normalizedValue = value === null || value === undefined ? '' : value;

    if (this._rule) {
      const fn = this._rule[this.bzRuleCondition === 'and' ? 'every' : 'some'].bind(this._rule);
      if (fn(r => r.test(normalizedValue)) || !value) {
        this.renderer.setProperty(this._inputElement, 'value', this.trim(this.bzTrim, normalizedValue));
        this._value = this.trim(this.bzTrim, normalizedValue);
      } else {
        this.onChange(this._value);
      }
    } else {
      this.renderer.setProperty(this._inputElement, 'value', this.trim(this.bzTrim, normalizedValue));
    }
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState?(isDisabled: boolean): void {
    this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', isDisabled);
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.bzRule.currentValue) {
      if (Array.isArray(changes.bzRule.currentValue)) {
        this._rule = changes.bzRule.currentValue.map(r => new RegExp(r));
      } else {
        this._rule = [new RegExp(changes.bzRule.currentValue)];
      }
    }
  }

  ngOnInit(): void {
  }

  private _input(value) {
    if (this._rule && (!this._compositionMode || (this._compositionMode && !this._composing))) {
      const fn = this._rule[this.bzRuleCondition === 'and' ? 'every' : 'some'].bind(this._rule);

      if (fn(r => r.test(value)) || !value) {
        this.onChange(this.trim(this.bzTrim, value || ''));
        this._value = this.trim(this.bzTrim, value || '');
      } else {
        this._inputElement.value = this._value;
      }
    } else {
      this.onChange(this.trim(this.bzTrim, value));
    }
  }

  private _compositionStart() {
    this._composing = true;
  }

  private _compositionEnd(value: any): void {
    this._composing = false;
    if (this._compositionMode) {
      this._input(value);
    }
  }

  private trim(isTrim, value) {
    if (isTrim && isString(value)) {
      return value.trim();
    }
    return value;
  }

}
